//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_CONTAINER_CCIRCULARBUFFER_H_
#define _BUOLA_CONTAINER_CCIRCULARBUFFER_H_

#include <buola/buola.h>
#include <buola/container/detail/cbufferstorage.h>

namespace buola {

template<typename tType,std::size_t vCapacity,typename tAlloc=std::allocator<tType> >
class CCircularBuffer : private detail::CBufferStorage<tType,vCapacity,tAlloc>
{
    typedef detail::CBufferStorage<tType,vCapacity,tAlloc> tStorage;
    typedef typename tStorage::TPointer TPointer;
    typedef typename tStorage::TReference TReference;

public:
    /// default constructor

    /// Initializes the buffer to be empty
    CCircularBuffer()
        :   mSize(0)
        ,   mDataBegin(0)
    {}

    /// constructor

    /// \p pCapacity specifies the size for dynamic buffers. This function is not valid for non-dynamic buffers.
    ///
    /// Initializes the buffer to be empty
    CCircularBuffer(std::size_t pCapacity)
        :   tStorage(pCapacity)
        ,   mSize(0)
        ,   mDataBegin(0)
    {}

    using tStorage::GetCapacity;
    using tStorage::GetMaxCapacity;

    /// returns the free capacity of the buffer, in elements
    std::size_t GetFreeCapacity() const   {   return GetCapacity()-mSize; }

    /// returns a pointer to the data
    TPointer GetData()   {   return tStorage::GetStorage()+mDataBegin;   }

    TReference operator[](std::size_t pPos) {   return tStorage::GetStorage()[(mDataBegin+pPos)%GetCapacity()]; }

    /// returns \c true if there is any data in the buffer
    bool HasData() const      {   return mSize;   }
    /// returns the number of elements stored in the buffer
    std::size_t GetSize() const    {   return mSize;   }

    /// appends \p pSize elements to the end of the buffer

    /// The function doesn't check (except in debug mode) if there is enough free capacity in the buffer.
    void PushData(const tType *pData,std::size_t pSize)
    {
        assert(mSize+pSize<=GetCapacity());
        std::size_t lDataEnd=(mDataBegin+mSize)%GetCapacity();
        std::size_t lToEnd=GetCapacity()-lDataEnd;
        if(pSize<=lToEnd)
            memcpy(tStorage::GetStorage()+lDataEnd,pData,pSize*sizeof(tType));
        else
        {
            memcpy(tStorage::GetStorage()+lDataEnd,pData,lToEnd*sizeof(tType));
            memcpy(tStorage::GetStorage(),pData+lToEnd,(pSize-lToEnd)*sizeof(tType));
        }
            
        mSize+=pSize;
    }

    /// gets \p pSize elements from the beginning of the buffer

    /// The function doesn't check (except in debug mode) if there are enough elements in the buffer.
    void PopData(tType *pData,std::size_t pSize) const
    {
        assert(pSize<=mSize);
        std::size_t lToEnd=GetCapacity()-mDataBegin;
        if(pSize<=lToEnd)
            memcpy(pData,tStorage::GetStorage()+mDataBegin,pSize*sizeof(tType));
        else
        {
            memcpy(pData,tStorage::GetStorage()+mDataBegin,lToEnd*sizeof(tType));
            memcpy(pData+lToEnd,tStorage::GetStorage(),(pSize-lToEnd)*sizeof(tType));
        }
    }

    /// removes \p pSize elements from the beginning of the buffer

    /// The function doesn't check (except in debug mode) if there are enough elements in the buffer.
    void ConsumeData(std::size_t pSize)
    {
        assert(pSize<=mSize);
        mSize-=pSize;
        mDataBegin=mSize?((mDataBegin+pSize)%GetCapacity()):0;
    }

    std::pair<TPointer,std::size_t> GetFillBuffer()
    {
        std::ptrdiff_t lDataEnd=(mDataBegin+mSize)%GetCapacity();

        return std::make_pair(tStorage::GetStorage()+lDataEnd,GetCapacity()-std::max((std::ptrdiff_t)mSize,lDataEnd));
    }

    void FilledData(std::size_t pSize)
    {
        assert(mSize+pSize<=GetCapacity());
        mSize+=pSize;
    }

private:
    std::size_t mSize;
    std::ptrdiff_t mDataBegin;
};

/*namespace buola*/ }

#endif
